package com.runyu.cordova.plugin.zebra.barcode;

import android.os.Build;
import android.os.Bundle;

import com.runyu.cordova.plugin.DeviceManager;
import com.runyu.cordova.plugin.JsActionSupport;
import com.symbol.emdk.EMDKBase;
import com.symbol.emdk.EMDKManager;
import com.symbol.emdk.ProfileManager;
import com.symbol.emdk.barcode.BarcodeManager;
import com.symbol.emdk.barcode.ScanDataCollection;
import com.symbol.emdk.barcode.Scanner;
import com.symbol.emdk.barcode.ScannerException;
import com.symbol.emdk.barcode.ScannerResults;
import com.symbol.emdk.barcode.StatusData;
import com.symbol.emdk.barcode.TokenizedData;
import com.symbol.emdk.notification.NotificationDevice;
import com.symbol.emdk.notification.NotificationException;
import com.symbol.emdk.notification.NotificationManager;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.LOG;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

public class ZebraBarcodeEventDispatcher implements Scanner.DataListener, EMDKManagerCreatedListener, Scanner.StatusListener, JsActionSupport {
	private final String TAG = "ZebraBarcodeEventDispatcher";
	private Scanner reader;
	private EMDKManager emdkManager;
	private final List<CallbackContext> callbackContexts = new ArrayList<>();
	private final List<CallbackContext> readyCallbackContexts = new ArrayList<>();

	private static final String EVENT_TYPE_REGISTER = "register";
	private static final String EVENT_TYPE_READ = "barcode";
	private static final String ACTION_ADD_BARCODE_EVENT_LISTENER = "addBarcodeEventListener";
	private static final String ACTION_REMOVE_BARCODE_EVENT_LISTENER = "removeBarcodeEventListener";
	private static final String ACTION_BARCODE_DEVICE_READY = "barcodeDeviceReady";

	@Override
	public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
		if (ACTION_ADD_BARCODE_EVENT_LISTENER.equals(action)) {
			this.addBarcodeCallbackContext(callbackContext);
			return true;
		}
		if (ACTION_REMOVE_BARCODE_EVENT_LISTENER.equals(action)) {
			if (args.length() > 0) {
				Object object = args.get(0);
				String callbackId = object.toString();
				boolean success = this.removeBarcodeCallbackContext(callbackId);
				if (!success) {
					callbackContext.error("Remove barcode event listener failed");
					return false;
				}
			}
			callbackContext.success();
			return true;
		}
		if (ACTION_BARCODE_DEVICE_READY.equals(action)) {
			this.addReadyCallbackContext(callbackContext);
			return true;
		}
		return false;
	}

	@Override
	public void onCreated(EMDKManager emdkManager) {
		this.emdkManager = emdkManager;
		boolean success = initBarcodeDevice();
		synchronized (this.readyCallbackContexts) {
			for (CallbackContext readyCallbackContext : this.readyCallbackContexts) {
				if(success){
					readyCallbackContext.success();
				}else{
					readyCallbackContext.error("BarcodeReader is not initialized");
				}
			}
			this.readyCallbackContexts.clear();
		}
	}

	private boolean initBarcodeDevice() {
		if(this.emdkManager == null){
			return false;
		}
		BarcodeManager barcodeManager = (BarcodeManager) this.emdkManager.getInstance(EMDKManager.FEATURE_TYPE.BARCODE);
		if(barcodeManager == null){
			LOG.e(TAG, "no BarcodeManager is available");
			return false;
		}
		Scanner device = barcodeManager.getDevice(BarcodeManager.DeviceIdentifier.INTERNAL_IMAGER1);
		if(device == null){
			LOG.e(TAG, "no Scanner is available");
			return false;
		}
		this.reader = device;

		this.reader.addDataListener(this);
		this.reader.addStatusListener(this);
		try {
			this.reader.enable();
		} catch (ScannerException e) {
			e.printStackTrace();
			this.reader = null;
			return false;
		}
		return true;
	}


	@Override
	public void onData(ScanDataCollection scanDataCollection) {
		LOG.i(TAG, "onData", scanDataCollection.getResult().getDescription());
		if (!isCurrentScanner(scanDataCollection.getFriendlyName())) return;
		if(ScannerResults.SUCCESS == scanDataCollection.getResult()){
			ScanDataCollection.ScanData scanData = scanDataCollection.getScanData().get(0);
			JSONObject jsonObject = new JSONObject();
			try {
				jsonObject.put("type", EVENT_TYPE_READ);
				jsonObject.put("data", scanData.getData());
			} catch (JSONException e) {
				e.printStackTrace();
			}
			for (CallbackContext callbackContext : this.callbackContexts) {
				PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, jsonObject);
				pluginResult.setKeepCallback(true);
				callbackContext.sendPluginResult(pluginResult);
			}
		}else{
			onFailure(scanDataCollection.getResult());
		}

	}

	private boolean isCurrentScanner(String scannerName) {
		String currentScannerName = this.reader.getScannerInfo().getFriendlyName();
		if(currentScannerName.equalsIgnoreCase(scannerName)){
			return true;
		}
		LOG.i(TAG, "onData scanner mismatch", scannerName);
		return false;
	}

	private void onFailure(ScannerResults result) {
		for (CallbackContext callbackContext : this.callbackContexts) {
			PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, result.getDescription());
			pluginResult.setKeepCallback(true);
			callbackContext.sendPluginResult(pluginResult);
		}
	}
	public void addBarcodeCallbackContext(CallbackContext callbackContext) {
		JSONObject jsonObject = new JSONObject();
		if (this.emdkManager==null || this.reader == null) {
			try {
				jsonObject.put("type", EVENT_TYPE_REGISTER);
				jsonObject.put("message", "BarcodeReader is not initialized");
			} catch (JSONException e) {
				e.printStackTrace();
			}
			callbackContext.error(jsonObject);
		} else {
			callbackContexts.add(callbackContext);
			try {
				jsonObject.put("type", EVENT_TYPE_REGISTER);
				jsonObject.put("callbackId", callbackContext.getCallbackId());
			} catch (JSONException e) {
				e.printStackTrace();
			}
			PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, jsonObject);
			pluginResult.setKeepCallback(true);
			callbackContext.sendPluginResult(pluginResult);
		}
	}

	


	public boolean removeBarcodeCallbackContext(String callbackId) {
		CallbackContext needRemoveCallbackContext = null;
		for (CallbackContext callbackContext : this.callbackContexts) {
			if (callbackContext.getCallbackId().equals(callbackId)) {
				needRemoveCallbackContext = callbackContext;
				break;
			}
		}
		if (needRemoveCallbackContext != null) {
			this.callbackContexts.remove(needRemoveCallbackContext);
		}
		return true;
	}

	public void onResume() {
		initBarcodeDevice();
	}

	public void onDestroy(){
		destroy();
	}

	private void destroy() {
		if (reader != null) {
			try {
				reader.disable();
				reader.removeDataListener(this);
				reader.release();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		if(emdkManager != null){
			emdkManager.release(EMDKManager.FEATURE_TYPE.BARCODE);
			emdkManager.release();
			emdkManager = null;
		}
		callbackContexts.clear();
	}


	public void addReadyCallbackContext(CallbackContext callbackContext) {
		if (isReady()) {
			callbackContext.success();
			return;
		}
		synchronized (this.readyCallbackContexts) {
			if (isReady()) {
				callbackContext.success();
				return;
			}
			this.readyCallbackContexts.add(callbackContext);
		}
	}

	private boolean isReady() {
		return this.emdkManager != null && this.reader != null;
	}

	@Override
	public List<String> getSupportAction() {
		List<String> actions = new ArrayList<>();
		actions.add(ACTION_ADD_BARCODE_EVENT_LISTENER);
		actions.add(ACTION_REMOVE_BARCODE_EVENT_LISTENER);
		actions.add(ACTION_BARCODE_DEVICE_READY);
		return actions;
	}

	@Override
	public void onStatus(StatusData statusData) {
		StatusData.ScannerStates state = statusData.getState();
		LOG.i(TAG, "onStatus", state.toString());
		if (state == StatusData.ScannerStates.IDLE) {
			if (!isCurrentScanner(statusData.getFriendlyName())) {
				return;
			}
			reader.triggerType = Scanner.TriggerType.HARD;
			// submit read
			if (!reader.isReadPending()) {
				try {
					reader.read();
				} catch (ScannerException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
